package loan.calc.calculator.common;

import loan.calc.constant.dict.PeriodSplitModeDictEnum;
import loan.calc.constant.dict.rate.RateTypeEnum;
import loan.calc.constant.dict.rate.RateUnitEnum;
import loan.calc.dto.LoanRateSegmentDTO;
import lombok.Data;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.Objects;

public class InterestCalculator {

    public static BigDecimal calculateInterest(BigDecimal baseAmount,
                                               LocalDate startDate,
                                               LocalDate endDate,
                                               List<LoanRateSegmentDTO> rateSegmentList,
                                               PeriodSplitModeDictEnum periodSplitMode,
                                               RateTypeEnum rateType) {
        return rateSegmentList.stream()
                .filter(segment -> segment.getRateType() == rateType)
                // .filter(segment -> !segment.getStartDate().isAfter(endDate) && !segment.getMaturityDate().isBefore(startDate))
                .map(segment -> {
                    // 开始日：取较晚日期
                    LocalDate startDateTmp = Objects.nonNull(segment.getStartDate()) && segment.getStartDate().isAfter(startDate) ? segment.getStartDate() : startDate;
                    // 结束日：取较晚日期
                    LocalDate endDateTmp = Objects.nonNull(segment.getMaturityDate()) && segment.getMaturityDate().isBefore(endDate) ? segment.getMaturityDate() : endDate;
                    return doCalculateInterest(baseAmount, startDateTmp, endDateTmp, segment.getRateUnit() ,segment.getExecutionRate(), segment.getYearBaseDay(), periodSplitMode);
                })
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }


    public static BigDecimal doCalculateInterest(BigDecimal baseAmount, LocalDate startDate, LocalDate endDate, RateUnitEnum rateUnit, BigDecimal executionRate, Integer yearBaseDay, PeriodSplitModeDictEnum periodSplitMode) {
        MonthsAndDaysDTO monthsAndDaysDTO = obtainMonthsAndDays(startDate, endDate, periodSplitMode);
        BigDecimal monthRate = InterestRateCalculator.getMonthRate(executionRate, rateUnit, yearBaseDay);
        BigDecimal monthInterest = baseAmount.multiply(BigDecimal.valueOf(monthsAndDaysDTO.getMonths())).multiply(monthRate);
        BigDecimal dayRate = InterestRateCalculator.getDayRate(executionRate, rateUnit, yearBaseDay);
        BigDecimal dayInterest = baseAmount.multiply(BigDecimal.valueOf(monthsAndDaysDTO.getDays())).multiply(dayRate);
        return monthInterest.add(dayInterest);
    }

    private static MonthsAndDaysDTO obtainMonthsAndDays(LocalDate startDate, LocalDate endDate, PeriodSplitModeDictEnum periodSplitMode) {
        MonthsAndDaysDTO monthsAndDays = new MonthsAndDaysDTO();
        if (!startDate.isBefore(endDate)) {
            return monthsAndDays;
        }
        switch (periodSplitMode) {// 先算整月数，再算零头天数
            case BEFORE_MONTH_THEN_DAY:
                for (LocalDate tmp = startDate; tmp.isBefore(endDate); tmp = tmp.plusMonths(1)) {
                    if (!tmp.plusMonths(1).isAfter(endDate)) {
                        monthsAndDays.incrementMonth();
                    } else {
                        monthsAndDays.setDays((int) (endDate.toEpochDay() - tmp.toEpochDay()));
                    }
                }
                break;
// 先算零头天数，再算整月数
            case BEFORE_DAY_THEN_MONTH:
                for (LocalDate tmp = endDate; tmp.isAfter(startDate); tmp = tmp.minusMonths(1)) {
                    if (!tmp.minusMonths(1).isBefore(startDate)) {
                        monthsAndDays.incrementMonth();
                    } else {
                        monthsAndDays.setDays((int) (tmp.toEpochDay() - startDate.toEpochDay()));
                    }
                }
                break;
            case ONLY_DAY:
                monthsAndDays.setDays((int) (endDate.toEpochDay() - startDate.toEpochDay()));
                break;
            default:
                throw new IllegalStateException("Unexpected period split mode: " + periodSplitMode);
        }
        return monthsAndDays;
    }


    @Data
    private static class MonthsAndDaysDTO {
        private Integer months;
        private Integer days;
        public MonthsAndDaysDTO() {
            this.months = 0;
            this.days = 0;
        }
        public void incrementMonth() {this.months++;}
        public void incrementMonths(int offset) {this.months += offset;}
        public void incrementDay() {this.days++;}
        public void incrementDays(int offset) {this.days += offset;}
    }
}
